/* Sentinel - IRC Statistical and Operator Services
** statserv.c - Statistical service interface
**
** Copyright W. Campbell and others.  See README for more details
** Some code Copyright: Jonathan George, Kai Seidler, ircd-hybrid Team,
**                      IRCnet IRCD developers.
**
** $Id: statserv.c 7 2010-02-01 19:17:42Z dubkat $
*/

#include "stats.h"
#include "struct.h"
#include "proto.h"
#include "match.h"
#include "s_string.h"
#include "slog.h"

#ifdef STATSERV

#define PERM_DENIED(u, n, s, t)	{ slog(DEFAULT_LOG,L_ERR,"Access Denied (%s) to %s", (s), \
                                         (u)->nick); spy("%s: %s Attempt by %s " \
                                                         "(failed)", (n), (s), (u)->nick); \
  sendto_dcc_flag((t), "%s: %s Attempt by %s " \
                  "(failed)", (n), (s), (u)->nick); \
  notice((u)->nick, (n), "Access Denied."); }

static char errstr[BUFSIZE];

static int s_server(struct User *, int, char **);
static int s_map(struct User *, int, char **);
static int s_version_map(struct User *, int, char **);
static int s_uptime(struct User *, int, char **);
static int s_netstats(struct User *, int, char **);
static int s_daily(struct User *, int, char **);
static int s_stats(struct User *, int, char **);
static int s_country(struct User *, int, char **);
static int s_tld(struct User *, int, char **);
static int s_tld_map(struct User *, int, char **);
static int s_operlist(struct User *, int, char **);
static int s_cping(struct User *, int, char **);
static int s_cversion(struct User *, int, char **);
static int s_update(struct User *, int, char **);
static int s_url(struct User *, int, char **);
static int s_status(struct User *, int, char **);
static int s_help(struct User *, int, char **);

static struct MsgTable stat_table[] =
  {
    { "HELP", 0, 0, NULL, s_help },
    { "SERVER", 0, 0, NULL, s_server },
    { "MAP", 0, 0, NULL, s_map },
    { "VERSIONMAP", 0, 0, NULL, s_version_map },
    { "UPTIME", 0, 0, NULL, s_uptime },
    { "NETSTATS", 0, 0, NULL, s_netstats },
    { "DAILY", 0, 0, NULL, s_daily },
    { "COUNTRY", 0, 0, NULL, s_country },
    { "TLD", 0, 0, NULL, s_tld },
    { "TLDMAP", 0, 0, NULL, s_tld_map },
    { "OPERLIST", 0, 0, NULL, s_operlist },
    { "STATS", 0, 0, NULL, s_stats },
    { "\001PING", 0, 0, NULL, s_cping },
    { "\001VERSION\001", 0, 0, NULL, s_cversion },
    { "UPDATE", 0, 0, NULL, s_update },
    { "URL", 0, 0, NULL, s_url },
    { "STATUS", 0, 0, NULL, s_status },
    { (char *)0, 0, 0, NULL, (int (*)()) 0 }
  };

static struct HelpTable help_table[] =
  {
    { "SERVER", "help/statserv/server", 0 },
    { "MAP", "help/statserv/map", 0 },
    { "NETSTATS", "help/statserv/netstats", 0 },
    { "DAILY", "help/statserv/daily", 0 },
    { "COUNTRY", "help/statserv/country", 0 },
    { "TLD", "help/statserv/tld", 0 },
    { "TLDMAP", "help/statserv/tldmap", 0 },
    { "OPERLIST", "help/statserv/operlist", 0 },
    { "STATS", "help/statserv/stats", 1 },
    { "UPDATE", "help/statserv/update", 1 },
    { "URL", "help/statserv/url", 0 },
    { "STATUS", "help/statserv/status", 0 },
    { "UPTIME", "help/statserv/uptime", 0 },
    { "VERSIONMAP", "help/statserv/versionmap", 0 },
    { (char *)0, (char *)0, 0 }
  };

void statserv(struct User *u, char *target, char *param)
{
  char *parv[MAXPARA];
  int parc = 0;
  int i;
  int status;
  static char buf[BUFSIZE];

  /* Clear the parv */
  for (i = 0; i < MAXPARA; i++)
    parv[i] = NULL;

  /* Clear the error string */
  errstr[0] = '\0';

  strlcpy(buf, param, BUFSIZE);

  if (param != NULL)
    parc = msgtoken(param, parv);

  netinfo.requests++;

  if (!is_channel(target) && flood(u))
    return ;

  if (parc == 0)
    return ;

  if (parv[0][0] != '\001')
    slog(DEFAULT_LOG, L_INFO, "%s received message from %s: %s", StatServ.nick,
         u->nick, buf);

  if (IsStOperOnly() && !IsOper(u))
  {
    if (parv[0][0] != '\001')
    {
      notice(u->nick, StatServ.nick,
              "This service is only available to IRCops.");
      return ;
    }
  }

  /* All the preliminary junk is done, now we have to go through
  ** the tables and call the handler for that command
  */
  for (i = 0; stat_table[i].handler; i++)
  {
    if (!strcasecmp(parv[0], stat_table[i].name))
    {
      status = stat_table[i].handler(u, parc, parv);
      if (status == 1)
        slog(DEFAULT_LOG, L_ERR, "Error with statserv command %s: %s",
             parv[0], (errstr) ? errstr : "No error message");
      /* Matched a value, done, then return */
      return ;
    }
  }
  /* If we get here, we did not get a valid command...
  ** Make sure not to say anything to the user if it was a 
  ** CTCP that was not matched
  */
  if (*parv[0] == '\001')
    return ;
  notice(u->nick, StatServ.nick, "Invalid command: %s", parv[0]);
  return ;
}

/* In all of these functions, parv[0] contains the command, followed
** by the parameters
*/

static int s_help(struct User *u, int parc, char *parv[])
{
  int i;

  spy("%s:  HELP Request by %s for %s", StatServ.nick, u->nick,
      (parv[1]) ? parv[1] : "NO QUERY");
  sendto_dcc_flag(CF_COMMANDS, "%s:  HELP Request by %s for %s",
                  StatServ.nick, u->nick, (parv[1]) ? parv[1] : "NO QUERY");

  if (parc < 2)
  {
    send_help_file(u->nick, StatServ.nick, "help/statserv/index", "index");
    if (u->myuser != NULL)
      send_help_file(u->nick, StatServ.nick, "help/statserv/aindex", "aindex");
    return 0;
  }

  for (i = 0; help_table[i].filename; i++)
  {
    if (!strcasecmp(parv[1], help_table[i].name))
    {
      if (help_table[i].myuonly)
      {
        if (u->myuser)
          send_help_file(u->nick, StatServ.nick, help_table[i].filename,
                         help_table[i].name);
        else
          notice(u->nick, StatServ.nick, "Permission Denied.");
      }
      else
      {
        send_help_file(u->nick, StatServ.nick, help_table[i].filename,
                       help_table[i].name);
      }

      return 0;
    }
  }

  notice(u->nick, StatServ.nick, "Unknown Help Topic: %s", parv[1]);
  snprintf(errstr, BUFSIZE, "Unknown HELP topic: %s", parv[1]);
  return 1;
}

static int s_server(struct User *u, int parc, char *parv[])
{
  struct Server *s = NULL;
  struct Stats *st = NULL;
  int found = 0;
  dlink_node *dl;

  if (parc < 2)
  {
    notice(u->nick, StatServ.nick, "Syntax: /msg %s SERVER <server name mask>",
            StatServ.nick);
    strcpy(errstr, "Missing parameter to SERVER");
    return 1;
  }

  spy("%s: SERVER %s Request by %s", StatServ.nick, parv[1], u->nick);
  sendto_dcc_flag(CF_COMMANDS, "%s: SERVER %s Request by %s",
                  StatServ.nick, parv[1], u->nick);

  DLINK_FOREACH(dl, shead.head)
  {
    st = dl->data;
    if (match(parv[1], st->name) == 0)
    {
      s = findserver(st->name);
      /* If it's a hidden server, skip */
      if ((s != NULL) && IsSHide(s) && !IsOper(u))
        continue;
      /* If it's split and it matches a hidden entry
      ** OR the entry was added after the server was
      ** online, skip
      */
      if (findhidden(parv[1]))
        continue;
      found++;
      notice(u->nick, StatServ.nick, "Statistics for %s since %s", st->name,
              sftime(st->starttime));

      if (s == NULL)
        notice(u->nick, StatServ.nick, "Server Last Seen:  %s",
                sftime(st->lastseen));
      if (s != NULL)
        notice(u->nick, StatServ.nick,
                "Current Users: %-3ld (%.0f%%) (%ld invisible)",
                s->users, (float)s->users / (float)netinfo.users * 100,
                s->invis);
      notice(u->nick, StatServ.nick, "Maximum Users: %-3ld %s", st->maxusers,
              sftime(st->t_maxusers));
      if (s != NULL)
        notice(u->nick, StatServ.nick, "Current Opers: %d", s->opers);
      notice(u->nick, StatServ.nick, "Maximum Opers: %-3d %s", st->maxopers,
              sftime(st->t_maxopers));

      if (s != NULL)
        notice(u->nick, StatServ.nick, "Current Away: %d", s->away);
      notice(u->nick, StatServ.nick, "Maximum Away: %-3d %s", st->maxaway,
              sftime(st->t_maxaway));
      notice(u->nick, StatServ.nick, "  IRCop Kills: %d", st->operkills);
      notice(u->nick, StatServ.nick, " Server Kills: %d", st->serverkills);
#if defined(IRCD_HYB7) || defined(IRCD_BAHAMUT) || defined(IRCD_HYBSVS)

      if ((s != NULL) && (s == me.uplink) && IsSetEOB())
        notice(u->nick, StatServ.nick, " EOB Received, burst time: %d seconds",
                me.eob_time - s->connected_since);
#endif

      if (st->numsplits > 0)
        notice(u->nick, StatServ.nick,
                "%s has split from the network %d time%s.", st->name,
                st->numsplits, (st->numsplits == 1) ? "" : "s");
      else
        notice(u->nick, StatServ.nick, "%s has never split from the network.",
                st->name);
      if (s && IsSHide(s))
        notice(u->nick, StatServ.nick, "Server is marked as hidden.");
#ifdef TRACK_VERSION

      if (s != NULL)
      {
        if (s->version[0] != '\0')
          notice(u->nick, StatServ.nick, "Version: %s", s->version);
        else
          notice(u->nick, StatServ.nick, "Version Unknown");
      }
#endif
#ifdef TRACK_ADMIN
      if (s != NULL)
      {
        if (s->admin1[0] != '\0')
          notice(u->nick, StatServ.nick, "Admin Info: %s", s->admin1);
        if (s->admin2[0] != '\0')
          notice(u->nick, StatServ.nick, "Admin Info: %s", s->admin2);
        if (s->admin3[0] != '\0')
          notice(u->nick, StatServ.nick, "Admin Info: %s", s->admin3);
      }
#endif

    }
  }
  if (found == 0)
  {
    notice(u->nick, StatServ.nick, "No information available on %s", parv[1]);
    return 0;
  }
  notice(u->nick, StatServ.nick, "End of Statistics.");

  return 0;
}

static int s_version_map(struct User *u, int parc, char *parv[])
{
#ifdef TRACK_VERSION
  struct Server *s = NULL;
  int i;
  dlink_node *dl;

  spy("%s: VERSIONMAP Request by %s", StatServ.nick, u->nick);
  sendto_dcc_flag(CF_COMMANDS, "%s: VERSIONMAP Request by %s",
                  StatServ.nick, u->nick);
  notice(u->nick, StatServ.nick, "[Server]                      [Version]");
  for (i = 0; i < S_TABLE_SIZE; i++)
  {
    DLINK_FOREACH(dl, serverhash[i].head)
    {
      s = dl->data;

      if (!IsSHide(s) || IsOper(u))
      {
        notice(u->nick, StatServ.nick, "%-30s %s", s->name,
                (s->version && s->version[0]) ? s->version : "Unknown Version");
      }
    }
  }
  notice(u->nick, StatServ.nick, "End of Version Map.");
#else

  notice(u->nick, StatServ.nick, "VERSIONMAP Disabled.");
#endif

  return 0;
}

static int s_map(struct User *u, int parc, char *parv[])
{
  struct Server *s;
  int i;
  dlink_node *dl;

  spy("%s: MAP Request by %s", StatServ.nick, u->nick);
  sendto_dcc_flag(CF_COMMANDS, "%s: MAP Request by %s", StatServ.nick,
                  u->nick);
  notice(u->nick, StatServ.nick,
          "Network Map [NAME] [USERS/MAX] [OPERS/MAX] [PERCENT]");
  for (i = 0; i < S_TABLE_SIZE; i++)
  {
    DLINK_FOREACH(dl, serverhash[i].head)
    {
      s = dl->data;

      if (!IsSHide(s) || IsOper(u))
      {
        notice(u->nick, StatServ.nick, "%-30s [%3d/%3d] [%2d/%2d] [%2.0f%%]",
                s->name, s->users, s->stats->maxusers, s->opers,
                s->stats->maxopers,
                (float) s->users / (float) netinfo.users * 100);
      }
    }
  }
  notice(u->nick, StatServ.nick, "%-30s [%4d/%4d] [%3d/%3d] [100%%]",
          "Totals:", netinfo.users, netinfo.maxusers, netinfo.opers,
          netinfo.maxopers);
  notice(u->nick, StatServ.nick, "End of Map.");

  return 0;
}

static int s_status(struct User *u, int parc, char *parv[])
{

  spy("%s: STATUS Request by %s", StatServ.nick, u->nick);
  sendto_dcc_flag(CF_COMMANDS, "%s: STATUS Request by %s",
                  StatServ.nick, u->nick);
  notice(u->nick, StatServ.nick, "Statistics Status:");
  notice(u->nick, StatServ.nick, "Reconnect Time: %d", settings.r_time);
  notice(u->nick, StatServ.nick, "Oper Only: %s",
          IsStOperOnly() ? "Enabled" : "Disabled");
  notice(u->nick, StatServ.nick, "HTML Dump: %s",
          IsHtml() ? "Enabled" : "Disabled");
  if (IsHtml())
  {
    notice(u->nick, StatServ.nick, "HTML File created every %d seconds",
            settings.html_time);
    notice(u->nick, StatServ.nick, "URL: %s", settings.url);
  }
  /* settings.name, settings.desc, settings.uplink all redundant, as
  ** they can be seen from LINKS or from whois StatServ.nick
  */
  notice(u->nick, StatServ.nick, "Set to oper: %s",
          IsStSetOper() ? "Enabled" : "Disabled");

  if (u->myuser)
  {
#ifdef ENABLE_QUOTE
    notice(u->nick, StatServ.nick, "QUOTE: Enabled");
#endif

    if (settings.flood)
    {
      notice(u->nick, StatServ.nick,
              "Flood Control: %d requests in %d seconds",
              settings.flood, settings.flood_time);
      notice(u->nick, StatServ.nick, "Flood Action: %s",
              IsFlood() ? "IGNORE" : "KILL");
    }
  }

  return 0;
}

static int s_uptime(struct User *u, int parc, char *parv[])
{
  spy("%s: UPTIME Request by %s", StatServ.nick, u->nick);
  sendto_dcc_flag(CF_COMMANDS, "%s: UPTIME Request by %s",
                  StatServ.nick, u->nick);
  notice(u->nick, StatServ.nick, "Statistics up %s",
          timediff(CurrentTime - me.t_start));
  notice(u->nick, StatServ.nick, "Statistic Requests: %d",
          netinfo.requests);
  return 0;
}

static int s_netstats(struct User *u, int parc, char *parv[])
{
  spy("%s: NETSTATS Request by %s", StatServ.nick, u->nick);
  sendto_dcc_flag(CF_COMMANDS, "%s: NETSTATS Request by %s",
                  StatServ.nick, u->nick);
  notice(u->nick, StatServ.nick, "Network Statistics:");
  notice(u->nick, StatServ.nick, "   Current Users: %ld", netinfo.users);
  notice(u->nick, StatServ.nick, "   Maximum Users: %-2ld %s", netinfo.maxusers,
          sftime(netinfo.t_maxusers));
  notice(u->nick, StatServ.nick, "   Client Connections: %ld since %s",
         netinfo.connections, sftime(me.t_start));
  notice(u->nick, StatServ.nick, "   Current Opers: %d", netinfo.opers);
  notice(u->nick, StatServ.nick, "   Maximum Opers: %-2d %s",
          netinfo.maxopers, sftime(netinfo.t_maxopers));
  notice(u->nick, StatServ.nick, "   Users Set Away: %-2d", netinfo.away);
  notice(u->nick, StatServ.nick, "   Maximum Away: %-2d %s", netinfo.maxaway,
          sftime(netinfo.t_maxaway));

  notice(u->nick, StatServ.nick, "   Users Set Invisible: %-2ld",
          netinfo.invis);

  notice(u->nick, StatServ.nick, "   Current Servers: %d", netinfo.servers);
  notice(u->nick, StatServ.nick, "   Maximum Servers: %-2d %s",
          netinfo.maxservers, sftime(netinfo.t_maxservers));
  notice(u->nick, StatServ.nick, "   Current Channels: %d", netinfo.channels);
  notice(u->nick, StatServ.nick, "   Maximum Channels: %-2d %s",
          netinfo.maxchannels, sftime(netinfo.t_maxchannels));
  notice(u->nick, StatServ.nick, "End of Information.");

  return 0;
}

static int s_daily(struct User *u, int parc, char *parv[])
{
  spy("%s: DAILY Request by %s", StatServ.nick, u->nick);
  sendto_dcc_flag(CF_COMMANDS, "%s: DAILY Request by %s",
                  StatServ.nick, u->nick);
  notice(u->nick, StatServ.nick, "Daily Network Statistics:");
  notice(u->nick, StatServ.nick, " ");
  notice(u->nick, StatServ.nick, "  Maximum Servers: %-2d %s", daily.servers,
         sftime(daily.t_servers));
  notice(u->nick, StatServ.nick, "  Maximum Users: %-2d %s", daily.users,
         sftime(daily.t_users));
  notice(u->nick, StatServ.nick, "  Maximum Opers: %-2d %s", daily.opers,
          sftime(daily.t_opers));
  notice(u->nick, StatServ.nick, "  Maximum Channels: %-2d %s", daily.channels,
          sftime(daily.t_channels));
  notice(u->nick, StatServ.nick, "  Connections Today: %ld", daily.connections);
  notice(u->nick, StatServ.nick,
          "All daily statistics are reset at midnight local time.");
  notice(u->nick, StatServ.nick, "End of Information.");

  return 0;
}

static int s_country(struct User *u, int parc, char *parv[])
{
  int i;
  struct TLD *t;
  char search[LINE];
  char search2[LINE];
  int matches = 0;

  if (parv[1] == NULL)
  {
    notice(u->nick, StatServ.nick,
            "Syntax: /msg %s COUNTRY <countryname>", StatServ.nick);
    notice(u->nick, StatServ.nick,
            "For additional help, /msg %s HELP", StatServ.nick);
    strcpy(errstr, "Missing parameter in COUNTRY");
    return 1;
  }

  /* Wrap the entered search string with *'s */
  ExpandParv(search2, LINE, 1, parc, parv);
  snprintf(search, LINE, "*%s*", search2);

  for (i = 0; i < T_TABLE_SIZE; i++)
  {
    for (t = tldlist[i]; t; t = t->next)
    {
      if (matches == COUNTRY_LIMIT)
      {
        notice(u->nick, StatServ.nick, "Too many matches, only %d returned",
                matches);
        return 0;
      }
      if (match(search, t->country) == 0)
      {
        matches++;
        notice(u->nick, StatServ.nick, "Match %d:  %s (%s)", matches,
                t->country, t->tld);
        if (t->native != NULL)
          notice(u->nick, StatServ.nick, "Native Name: %s", t->native);
        /* There can be multiple matches for the
        ** same strings, consider Korea or Congo
        */
      }
    }
  }

  return 0;
}

static int s_tld(struct User *u, int parc, char *parv[])
{
  struct TLD *tmp;
  char *tld = parv[1];

  if (!tld)
  {
    notice(u->nick, StatServ.nick, "Syntax: /msg %s TLD <tld>", StatServ.nick);
    notice(u->nick, StatServ.nick, "For additional help, /msg %s HELP",
           StatServ.nick);
    strcpy(errstr, "Missing parameter in TLD");
    return 1;
  }

  if (*tld == '*')
    tld++;
  if (*tld == '.')
    tld++;

  /* Catch it when it's just * or . */
  if (*tld == '\0')
  {
    notice(u->nick, StatServ.nick, "Syntax: /msg %s TLD <tld>", StatServ.nick);
    notice(u->nick, StatServ.nick, "For additional help, /msg %s HELP",
           StatServ.nick);
    strcpy(errstr, "Missing parameter in TLD");
    return 1;
  }

  spy("%s: TLD %s Request by %s", StatServ.nick, tld, u->nick);
  sendto_dcc_flag(CF_COMMANDS, "%s: TLD %s Request by %s", StatServ.nick,
                  tld, u->nick);

  tmp = findtld(tld);

  if (tmp == NULL)
    notice(u->nick, StatServ.nick, "Top Level Domain %s does not exist.", tld);
  else
  {
    notice(u->nick, StatServ.nick, "%s (%s)", tmp->country, tmp->tld);
    if (tmp->native != NULL)
      notice(u->nick, StatServ.nick, "Native Name: %s", tmp->native);
  }

  return 0;
}

static int s_tld_map(struct User *u, int parc, char *parv[])
{
  struct TLD *t;
  int i;

  spy("%s: TLDMAP Request by %s", StatServ.nick, u->nick);
  sendto_dcc_flag(CF_COMMANDS, "%s: TLDMAP Request by %s",
                  StatServ.nick, u->nick);
  notice(u->nick, StatServ.nick, "Top Level Domain Statistics:");
  notice(u->nick, StatServ.nick, "   TLD Number / Percent  Country");
  notice(u->nick, StatServ.nick, "   --- ------   -------  -------");

  for (i = 0; i < T_TABLE_SIZE; i++)
  {
    for (t = tldlist[i]; t; t = t->next)
    {
      if (t->users != 0)
        notice(u->nick, StatServ.nick, "%9s %3d (%2.0f%%) -> %s", t->tld,
                t->users, (float)t->users / (float)netinfo.users * 100,
                t->country);
    }
  }
  notice(u->nick, StatServ.nick, "End of Information.");

  return 0;
}

static int s_operlist(struct User *u, int parc, char *parv[])
{
  dlink_node *dl;
  int j = 0;
  int away = 0;
  struct User *ul;
  char *server = parv[2];

  spy("%s: OPERLIST Request by %s", StatServ.nick, u->nick);
  sendto_dcc_flag(CF_COMMANDS, "%s: OPERLIST Request by %s",
                  StatServ.nick, u->nick);
  if (parv[1] && !strcasecmp(parv[1], "NOAWAY"))
  {
    away = 1;
  }
  if (!away && parv[1] && strchr(parv[1], '.'))
    server = parv[1];

  notice(u->nick, StatServ.nick, "On-Line IRCops:");

  DLINK_FOREACH(dl, global_oper_list.head)
  {
    ul = (struct User *) dl->data;
    if (away && IsAway(ul))
      continue;
    if (IsSHide(ul->server) && !IsOper(u))
      continue;
    if (!IsOper(ul))
      continue;
    if (!server)
    {
      j++;
      notice(u->nick, StatServ.nick,
#ifdef OPER_MODES
              "[%2d] [%c] %-15s %s", j, opermode(ul), ul->nick,
#else
              "[%2d] %-15s %s", j, ul->nick,
#endif
              ul->server->name);
      continue;
    }
    else
    {
      if (strcasecmp(server, u->server->name))
        continue;
      j++;
      notice(u->nick, StatServ.nick,
#ifdef OPER_MODES
              "[%2d] [%c] %-15s %s", j, opermode(ul),
#else
              "[%2d] %-15s %s", j,
#endif
              ul->nick, ul->server->name);
      continue;
    }
  }
  notice(u->nick, StatServ.nick, "End of Listing.");

  return 0;
}

static int s_stats(struct User *u, int parc, char *parv[])
{
  struct Stats *st;

  if (!u->myuser || !IsSetOperStats(u->myuser))
  {
    PERM_DENIED(u, StatServ.nick, "STATS", CF_COMMANDS);
    return 0;
  }

  if (parc < 2)
  {
    notice(u->nick, StatServ.nick, "Syntax: /msg %s STATS [DEL|LIST|COPY]",
            StatServ.nick);
    notice(u->nick, StatServ.nick, "For additional help, /msg %s HELP",
           StatServ.nick);
    strcpy(errstr, "Missing parameter in STATS");
    return 1;
  }
  if (!strcasecmp(parv[1], "LIST"))
  {
    int i = 1;
    dlink_node *dl;

    spy("%s: STATS LIST Request by %s", StatServ.nick, u->nick);
    sendto_dcc_flag(CF_COMMANDS, "%s: STATS LIST Request by %s",
                    StatServ.nick, u->nick);
    notice(u->nick, StatServ.nick, "Statistics Database:");
    DLINK_FOREACH(dl, shead.head)
    {
      st = (struct Stats *) dl->data;
      notice(u->nick, StatServ.nick, "[%-2d] %s", i, st->name);
      i++;
    }
    notice(u->nick, StatServ.nick, "End of List.");
    slog(DEFAULT_LOG, L_INFO, "%s requested STATS LIST.", u->nick);
  }
  else if (!strcasecmp(parv[1], "DEL"))
  {
    dlink_node *dl;

    if (parc < 3)
    {
      notice(u->nick, StatServ.nick, "Syntax: /msg %s STATS DEL <name>",
              StatServ.nick);
      notice(u->nick, StatServ.nick, "For additonal help, /msg %s HELP",
              StatServ.nick);
      strcpy(errstr, "Missing parameter in STATS");
      return 1;
    }
    st = findstats(parv[2]);
    if (!st)
    {
      spy("%s: STATS DEL %s Attempt by %s (failed)",
          StatServ.nick, parv[2], u->nick);
      sendto_dcc_flag(CF_COMMANDS,
                      "%s: STATS DEL %s Attempt by %s (failed)",
                      StatServ.nick, parv[2], u->nick);
      notice(u->nick, StatServ.nick, "%s is not in the database.", parv[2]);
      snprintf(errstr, BUFSIZE, "Unknown server: %s",
               parv[2]);
      return 1;
    }
    DLINK_FOREACH(dl, shead.head)
    {
      st = (struct Stats *) dl->data;
      if (!strcasecmp(parv[2], st->name))
      {
        dlink_delete(dl, &shead);
        dlink_free(dl);
        free(st);
        memcounts.stats--;
        break;
      }
    }
    spy("%s: STATS DEL %s Request by %s", StatServ.nick, parv[2],
        u->nick);
    sendto_dcc_flag(CF_COMMANDS, "%s: STATS DEL %s Request by %s",
                    StatServ.nick, parv[2],
                    u->nick);
    notice(u->nick, StatServ.nick, "Removed %s from the database.", parv[2]);
    slog(DEFAULT_LOG, L_WARNING, "%s requested STATS DEL %s", u->nick, parv[2]);
  }
  else if (!strcasecmp(parv[1], "COPY"))
  {
    struct Server *s;
    struct Stats *st2;
    dlink_node *dl;

    if (parc < 4)
    {
      notice(u->nick, StatServ.nick,
              "Syntax: /msg %s STATS COPY <name> <newname>", StatServ.nick);
      strcpy(errstr, "Missing parameter in STATS");
      return 1;
    }

    st = findstats(parv[2]);
    if (!st)
    {
      notice(u->nick, StatServ.nick, "No entry in the database for %s",
             parv[2]);
      snprintf(errstr, BUFSIZE, "Unknown stats entry: %s",
               parv[2]);
      return 1;
    }
    s = findserver(parv[2]);
    if (s)
    {
      notice(u->nick, StatServ.nick, "Server %s is online!", parv[2]);
      snprintf(errstr, BUFSIZE, "Server %s is online",
               parv[2]);
      return 1;
    }

    /* Wait until we are sure before removing a statistics
    ** entry
    */
    DLINK_FOREACH(dl, shead.head)
    {
      st2 = (struct Stats *) dl->data;
      if (!strcasecmp(parv[3], st2->name))
      {
        dlink_delete(dl, &shead);
        dlink_free(dl);
        free(st2);
        memcounts.stats--;
        break;
      }
    }

    s = NULL;
    strlcpy(st->name, parv[3], sizeof(st->name));
    s = findserver(st->name);
    if (s)
      s->stats = st;
    notice(u->nick, StatServ.nick, "Moved database entry for %s to %s", parv[2],
            parv[3]);
    slog(DEFAULT_LOG, L_WARNING, "%s requested STATS COPY %s -> %s", u->nick,
         parv[2], parv[3]);
    spy("%s: STATS COPY %s %s Request by %s", StatServ.nick,
        parv[2], parv[3], u->nick);
    sendto_dcc_flag(CF_COMMANDS,
                    "%s: STATS COPY %s %s Request by %s", StatServ.nick,
                    parv[2], parv[3], u->nick);
  }
  else
  {
    notice(u->nick, StatServ.nick, "Invalid Argument.");
    notice(u->nick, StatServ.nick, "For help, /msg %s HELP", StatServ.nick);
    strcpy(errstr, "Invalid argument in STATS");
    return 1;
  }

  SaveStats();
  return 0;
}

static int s_cping(struct User *u, int parc, char *parv[])
{
  char command[BUFSIZE];

  ExpandParv(command, BUFSIZE, 1, parc, parv);
  if (command[0] == '\0')
    return 1;
  sts(":%s NOTICE %s :\001PING %s", StatServ.nick, u->nick, command);
  slog(DEFAULT_LOG, L_INFO, "CTCP PING from %s!%s@%s", u->nick, u->username,
       u->hostname);

  return 0;
}

static int s_cversion(struct User *u, int parc, char *parv[])
{
  sts(":%s NOTICE %s :\001VERSION %s %s -> %s\001", StatServ.nick,
      u->nick, stats_version, VersionFlags(), stats_date);
  slog(DEFAULT_LOG, L_INFO, "CTCP VERSION from %s!%s@%s", u->nick, u->username,
       u->hostname);

  return 0;
}

static int s_update(struct User *u, int parc, char *parv[])
{
  if (!u->myuser || !IsSetOperStats(u->myuser))
  {
    PERM_DENIED(u, StatServ.nick, "UPDATE", CF_COMMANDS);
    return 0;
  }

  if (!IsHtml())
  {
    notice(u->nick, StatServ.nick, "HTML Update Disabled");
    return 0;
  }

  DumpHTML();
  spy("%s: UPDATE Request by %s", StatServ.nick, u->nick);
  sendto_dcc_flag(CF_COMMANDS, "%s: UPDATE Request by %s", StatServ.nick,
                  u->nick);
  notice(u->nick, StatServ.nick, "HTML Update Forced");

  slog(DEFAULT_LOG, L_NOTICE, "%s!%s@%s forces an HTML Update", u->nick, u->username,
       u->hostname);
  return 0;
}

static int s_url(struct User *u, int parc, char *parv[])
{
  if (!IsHtml())
  {
    notice(u->nick, StatServ.nick, "HTML Dump Disabled");
    return 0;
  }

  notice(u->nick, StatServ.nick, "HTML Status URL: %s", settings.url);
  spy("%s: URL Request by %s", StatServ.nick, u->nick);
  sendto_dcc_flag(CF_COMMANDS, "%s: URL Request by %s", StatServ.nick,
                  u->nick);
  slog(DEFAULT_LOG, L_INFO, "%s!%s@%s Looks up URL", u->nick, u->username, u->hostname);

  return 0;
}

#endif
